/**
* @file pit.cpp
* Programmable Interval Time interface for X86 platform.
* Contains data structures and function for PIT managment:
* to send commands to PIT, to send/read data to/from PIT, init PIT, 
* Inicialization Control Words constants for PIT etc...
*
* Copyrights 2010 Michal Saman, m.saman@designplus.cz
* This source code is release under the Apache License 2.0.
*/
#ifndef ARCH_X86
#error "[pit.c] is platform-independent. Define ARCH_X86."
#endif

#include <stdint.h>
#include <hal/pit.h>
#include <hal/hal.h>
#include <hal/idt.h>
#include <proc/process.h>
#include <main/kprintf.h>

/** 
* PIT controller port addresses.
*/
#define PIT_REG_COUNTER0	0x40
#define PIT_REG_COUNTER1	0x41
#define PIT_REG_COUNTER2	0x42
#define PIT_REG_COMMAND		0x43

/** Global Tick count */
volatile uint32_t	pit_ticks=0;

/** Test if PIT is initialized */
static bool					pit_is_init=false;

/**
* Send command to PIT.
* @param cmd	Operation Command Word for PIT
*/
extern void pit_send_command(uint8_t cmd) {
	outportb(PIT_REG_COMMAND, cmd);
}

/**
* Function set data to PIT to specified counter.
* @param data		Data that we wants to send
* @param counter	Which counter we wants send data
*/
extern void pit_send_data(uint16_t data, uint8_t counter) {
	uint8_t reg = (counter==PIT_OCW_COUNTER_0) ? PIT_REG_COUNTER0 :
		((counter==PIT_OCW_COUNTER_1) ? PIT_REG_COUNTER1 : PIT_REG_COUNTER2);
	
	outportb(reg, (uint8_t) data);
}

/**
* Read data from a specified PIT counter.
* @param counter		Counter from we wants read data
* @return Data from counter
*/
extern uint32_t pit_read_data(uint8_t counter) {
	uint8_t reg = (counter==PIT_OCW_COUNTER_0) ? PIT_REG_COUNTER0 :
		((counter==PIT_OCW_COUNTER_1) ? PIT_REG_COUNTER1 : PIT_REG_COUNTER2);
	return inportb(reg);
}

/**
* Sets pit_ticks variable and returns previous value.
* @param i		New value form pit_ticks
* @return Previous value of pit_ticks
*/
extern uint32_t pit_set_tick_count(uint32_t i) {
	uint32_t ret = pit_ticks;
	pit_ticks = i;
	return ret;
}

/** 
* Get current tick count.
* @returns Current value of pit_ticks.
*/
extern uint32_t pit_get_tick_count() {
	return pit_ticks;
}

/**
* Start counter at specified frequency.
* @param freq		Frequency for counter
* @param counter	Counter that we want to start
* @param mode		Mode for counter
*/
extern void pit_start_counter(uint32_t freq, uint8_t counter, uint8_t mode) {
	if (freq==0)
		return;
	uint16_t divisor = (uint16_t) (1193181 / (uint16_t) freq);
	// send Operational command
	uint8_t ocw = 0;
	ocw = (ocw & ~PIT_OCW_MASK_MODE) | mode;
	ocw = (ocw & ~PIT_OCW_MASK_RL) | PIT_OCW_RL_DATA;
	ocw = (ocw & ~PIT_OCW_MASK_COUNTER) | counter;
	pit_send_command(ocw);
	// set frequency rate
	pit_send_data(divisor & 0xFF, 0);
	pit_send_data((divisor >> 8) & 0xFF, 0);
	// reset tick count
	pit_ticks = 0;
}

/**
* Handler function for PIT interrupt request.
*/
void pit_irq_handler() {
        __asm__ volatile ("cli");
        __asm__ volatile ("pushal");
    	pit_ticks++;
    	end_hw_interrupt(0);
        __asm__ volatile ("popal");
        __asm__ volatile ("sti");
        __asm__ volatile ("iretl");
}

/**
* Inicialize Programable Interval Timer.
*/
void __attribute__ ((cdecl)) pit_init() {
	// install out interrupt handler (irq0 uses interrupt 32 - 0x20)
	set_handler_interrupt(hal_pic_base0 + 0, pit_irq_handler);
	// we are inicialized
	pit_is_init = true;
}

void pit_reload_handler(void (*fn) ()) {
	set_handler_interrupt(hal_pic_base0 + 0, fn);
}

/**
* Returns state of PIT.
* @return Is PIT init?
*/
bool __attribute__ ((cdecl)) pit_is_inicialized() {
	return pit_is_init;
}
